﻿using RoadQualityEvaluatorData.Domain.Entities;
using RoadQualityEvaluatorData.DomainServices.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RoadQualityEvalAPI.Helpers
{
    public class MapSectionHelper
    {
        private MapSectionRepository _mapSectionRepo;

        public MapSectionRepository MapSectionRepo
        {
            get
            {
                return this._mapSectionRepo;
            }
        }

        public MapSectionHelper()
        {
            _mapSectionRepo = new MapSectionRepository();
        }



        #region MapSectionHelpers

        public MapSection AssignLocationToMapSection(Location location)
        {
            double minimumDistance = 1000;
            MapSection closestMapSectionToCurrentLocation = new MapSection();

            foreach (var mapSection in _mapSectionRepo.GetAll())
            {
                GeneralHelpers.Point mapSectionProjectionPoint;
                var distanceFromLocationToMapSection_Rough = GeneralHelpers.GetDistance(new GeneralHelpers.Point(location.Latitude, location.Longitude), new GeneralHelpers.Point(mapSection.StartLocation.Latitude, mapSection.StartLocation.Longitude), new GeneralHelpers.Point(mapSection.EndLocation.Latitude, mapSection.EndLocation.Longitude), out mapSectionProjectionPoint);
                var distanceFromLocationToProjectionPoint = GeneralHelpers.ComputeDistanceBetweenTwoLocationPoints(location.Latitude, location.Longitude, mapSectionProjectionPoint.X, mapSectionProjectionPoint.Y);

                if (distanceFromLocationToProjectionPoint < minimumDistance)
                {
                    minimumDistance = distanceFromLocationToProjectionPoint;
                    closestMapSectionToCurrentLocation = mapSection;
                }
            }

            if (minimumDistance <= 100)
            {
                return closestMapSectionToCurrentLocation;
            }
            else
                return null;
        }




        public static int ConvertSpeedToIndex(double speed)
        {
            int speedIndex = 0;
            if (speed < 10)
                speedIndex = 1;
            else if (speed >= 10 && speed < 20)
                speedIndex = 3;
            else if (speed >= 20 && speed < 30)
                speedIndex = 5;
            else if (speed >= 30 && speed < 40)
                speedIndex = 6;
            else if (speed >= 40 && speed < 50)
                speedIndex = 7;
            else if (speed > 50 && speed <= 60)
                speedIndex = 9;
            else if (speedIndex > 60)
                speedIndex = 10;

            return speedIndex;
        }

        public static int ConvertBadPotholesCountToIndex(int badPotholesCount)
        {
            if (badPotholesCount == 0)
                return 10;
            else if (badPotholesCount == 1)
                return 6;
            else if (badPotholesCount >= 2 && badPotholesCount < 5)
                return 4;
            else
                return 1;

        }

        public static int ConvertNormalPotholesCountToIndex(int normalPotholesCount)
        {
            if (normalPotholesCount == 0)
                return 10;
            else if (normalPotholesCount == 1)
                return 9;
            else if (normalPotholesCount == 2)
                return 7;
            else if (normalPotholesCount > 2 && normalPotholesCount < 6)
                return 5;
            else if (normalPotholesCount > 6 && normalPotholesCount < 9)
                return 2;
            else
                return 1;

        }


        public static double UpdateDistanceAccordingToQuality(double distance, double qualityIndex)
        {
            if (qualityIndex < 1)
                return distance * 10;
            else if (qualityIndex >= 1 && qualityIndex < 2)
                return distance * 8;
            else if (qualityIndex >= 2 && qualityIndex < 3)
                return distance * 5;
            else if (qualityIndex >= 3 && qualityIndex < 4)
                return distance * 3;
            else if (qualityIndex >= 4 && qualityIndex < 5)
                return distance * 2;
            else if (qualityIndex >= 5 && qualityIndex < 6)
                return distance;
            else if (qualityIndex >= 6 && qualityIndex < 7)
                return (distance - 0.2 * distance);
            else if (qualityIndex >= 7 && qualityIndex < 8)
                return (distance - 0.4 * distance);
            else if (qualityIndex >= 8 && qualityIndex < 9)
                return (distance - 0.6 * distance);
            else if (qualityIndex >= 9)
                return (distance - 0.9 * distance);

            return distance;
        }

        #endregion


        // Bad Dijkstra (not working well)!!!
        #region Dijkstra1
        /*
        public struct Vertex
        {
            public string Label { get; set; }
            public double X { get; set; }
            public double Y { get; set; }

            public Vertex(string label, double x, double y)
                : this()
            {
                this.Label = label;
                this.X = x;
                this.Y = y;
            }

        }

        public struct Edge
        {
            public string Id { get; set; }
            public Vertex SourceVertex { get; set; }
            public Vertex DestinationVertex { get; set; }
            public double Distance { get; set; }
            public int Weight { get; set; }


            public Edge(string id, Vertex v1, Vertex v2, double distance, int cost)
                : this()
            {
                this.Id = id;
                this.SourceVertex = v1;
                this.DestinationVertex = v2;
                this.Distance = distance;
                this.Weight = cost;
            }

        }

        public class Graph
        {
            public List<Vertex> Vertices { get; set; }
            public List<Edge> Edges { get; set; }

            public Graph(List<Vertex> vertices, List<Edge> edges)
            {
                Vertices = vertices;
                Edges = edges;
            }
        }

        public class Dijskstra
        {
            private List<Vertex> Nodes;
            private List<Edge> Edges;
            private HashSet<Vertex?> SettledNodes;
            private HashSet<Vertex?> UnSettledNodes;
            private Dictionary<Vertex, Vertex?> Predecessors;
            private Dictionary<Vertex?, int> Distance;


            public Dijskstra(Graph graph)
            {
                this.Nodes = new List<Vertex>(graph.Vertices);
                this.Edges = new List<Edge>(graph.Edges);
            }

            public void Execute(Vertex source)
            {
                SettledNodes = new HashSet<Vertex?>();
                UnSettledNodes = new HashSet<Vertex?>();
                Distance = new Dictionary<Vertex?, int>();
                Predecessors = new Dictionary<Vertex, Vertex?>();


                Distance.Add(source, 0);
                UnSettledNodes.Add(source);
                while (UnSettledNodes.Count > 0)
                {
                    Vertex? node = GetMinimum(UnSettledNodes);
                    SettledNodes.Add(node);
                    UnSettledNodes.Remove(node);
                    FindMinimalDistances(node);
                }
            }


            private void FindMinimalDistances(Vertex? node)
            {
                List<Vertex> adjacentNodes = GetNeighbors(node);
                foreach (Vertex target in adjacentNodes)
                {
                    if (GetShortestDistance(target) > GetShortestDistance(node)
                        + GetDistance(node, target))
                    {
                        Distance.Add(target, GetShortestDistance(node)
                            + GetDistance(node, target));
                        Predecessors.Add(target, node);
                        UnSettledNodes.Add(target);
                    }
                }

            }

            private int GetDistance(Vertex? node, Vertex target)
            {
                foreach (Edge edge in Edges)
                {
                    if (edge.SourceVertex.Equals(node)
                        && edge.DestinationVertex.Equals(target))
                    {
                        return edge.Weight;
                    }
                }
                throw new Exception("Should not happen");
            }

            private List<Vertex> GetNeighbors(Vertex? node)
            {
                List<Vertex> neighbors = new List<Vertex>();
                foreach (Edge edge in Edges)
                {
                    if (edge.SourceVertex.Equals(node)
                        && !IsSettled(edge.DestinationVertex))
                    {
                        neighbors.Add(edge.DestinationVertex);
                    }
                }
                return neighbors;
            }


            private Vertex? GetMinimum(HashSet<Vertex?> vertexes)
            {
                Vertex? minimum = null;
                foreach (Vertex vertex in vertexes)
                {
                    if (minimum == null)
                    {
                        minimum = vertex;
                    }
                    else
                    {
                        if (GetShortestDistance(vertex) < GetShortestDistance(minimum))
                        {
                            minimum = vertex;
                        }
                    }
                }
                return minimum;
            }


            private Boolean IsSettled(Vertex vertex)
            {
                return SettledNodes.Contains(vertex);
            }


            private int GetShortestDistance(Vertex? destination)
            {
                try
                {
                    return Distance[destination];
                }
                catch (Exception ex)
                {
                    return int.MaxValue;
                }
                /*
                if (Distance[destination] == null)
                {
                    return int.MaxValue;
                }
                else
                {
                    return d;
                }
                */
    }

    /*
     * This method returns the path from the source to the selected target and
     * NULL if no path exists
     */
    /*
    public List<Vertex> GetPath(Vertex target)
    {
        List<Vertex> path = new List<Vertex>();
        Vertex step = target;

        // check if a path exists
        try
        {
            var existingPath = Predecessors[step];

            path.Add(step);
        }
        catch (Exception ex)
        {
            return null;
        }
                
        /*
        if ((Nullable<Vertex>)Predecessors[step] == null)
        {
            return null;
        }

        path.Add(step);
        */

    /*
    bool isNotNull = true;


    while (isNotNull)
    {
        try
        {
            step = (Vertex)Predecessors[step];
        }
        catch (Exception ex)
        {
            isNotNull = false;
        }

        path.Add(step);
    }

    // Put it into the correct order
    path.Reverse();
    return path;
}

}
*/
        #endregion


        #region Dijkstra_2

        public class Edge
        {
            public String Id { get; private set; }
            public Vertex Source { get; private set; }
            public Vertex Destination { get; private set; }
            public double Weight { get; private set; }
            public double Distance { get; private set; }

            public Edge(String id, Vertex source, Vertex destination, double weight, double dist)
            {
                this.Id = id;
                this.Source = source;
                this.Destination = destination;
                this.Weight = weight;
                this.Distance = dist;
            }
        }


        public class Vertex
        {
            public String Name { get; private set; }
            public double Latitude { get; private set; }
            public double Longitude { get; private set; }

            public Vertex(String name, double lat, double lon)
            {
                this.Name = name;
                this.Latitude = lat;
                this.Longitude = lon;
            }

        }

        public class Dijkstra
        {

            private List<Vertex> _nodes;
            private List<Edge> _edges;
            private List<Vertex> _basis;
            private Dictionary<string, double> _dist;
            private Dictionary<string, Vertex> _previous;

            public Dijkstra(List<Edge> edges, List<Vertex> nodes)
            {

                Edges = edges;
                Nodes = nodes;
                Basis = new List<Vertex>();
                Dist = new Dictionary<string, double>();
                Previous = new Dictionary<string, Vertex>();

                // record node 
                foreach (Vertex n in Nodes)
                {
                    Previous.Add(n.Name, null);
                    Basis.Add(n);
                    Dist.Add(n.Name, double.MaxValue);
                }
            }

            /// Calculates the shortest path from the start
            ///  to all other nodes
            public void calculateDistance(Vertex start)
            {
                Dist[start.Name] = 0;

                while (Basis.Count > 0)
                {
                    Vertex u = getNodeWithSmallestDistance();
                    if (u == null)
                    {
                        Basis.Clear();
                    }
                    else
                    {
                        foreach (Vertex v in getNeighbors(u))
                        {
                            double alt = Dist[u.Name] + getDistanceBetween(u, v);
                            if (alt < Dist[v.Name])
                            {
                                Dist[v.Name] = alt;
                                Previous[v.Name] = u;
                            }
                        }
                        Basis.Remove(u);
                    }
                }
            }

            public List<Vertex> getPathTo(Vertex d)
            {
                List<Vertex> path = new List<Vertex>();

                path.Insert(0, d);

                while (Previous[d.Name] != null)
                {
                    d = Previous[d.Name];
                    path.Insert(0, d);
                }

                return path;
            }

            public Vertex getNodeWithSmallestDistance()
            {
                double distance = double.MaxValue;
                Vertex smallest = null;

                foreach (Vertex n in Basis)
                {
                    if (Dist[n.Name] < distance)
                    {
                        distance = Dist[n.Name];
                        smallest = n;
                    }
                }

                return smallest;
            }


            public List<Vertex> getNeighbors(Vertex n)
            {
                List<Vertex> neighbors = new List<Vertex>();

                foreach (Edge e in Edges)
                {
                    if (e.Source.Equals(n) && Basis.Contains(n))
                    {
                        neighbors.Add(e.Destination);
                    }
                }

                return neighbors;
            }

            public double getDistanceBetween(Vertex o, Vertex d)
            {
                foreach (Edge e in Edges)
                {
                    if (e.Source.Equals(o) && e.Destination.Equals(d))
                    {
                        return e.Distance;
                    }
                }

                return 0;
            }


            public List<Vertex> Nodes
            {
                get { return _nodes; }
                set { _nodes = value; }
            }

            public List<Edge> Edges
            {
                get { return _edges; }
                set { _edges = value; }
            }

            public List<Vertex> Basis
            {
                get { return _basis; }
                set { _basis = value; }
            }

            public Dictionary<string, double> Dist
            {
                get { return _dist; }
                set { _dist = value; }
            }

            public Dictionary<string, Vertex> Previous
            {
                get { return _previous; }
                set { _previous = value; }
            }
        }


        #endregion



}
